﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Net;

namespace GShare1._0.Client
{
    public class Download
    {
        //Add Connection here to the chosen client
        public void BeginConnection()
        {

        }
        private delegate void uploadAsyncDelegate();
        private uploadAsyncDelegate asyncUploadDelegate;
        private string filename;
        private ClientConnection client;
        private bool alive;
        private ClientState state = new ClientState();

        private Socket socket;

        public Socket Socket
        {
            get { return socket; }
            set { socket = value; }
        }
        
        private bool serverReceiving = true;

        /// <summary>
        /// Flag used to check if server has ended the upload connection
        /// </summary>
        public bool ServerReceiving
        {
            get { return serverReceiving; }
            set { serverReceiving = value; }
        }
        
        public bool Alive
        {
            get { return alive; }
            set { alive = value; }
        }
        private AutoResetEvent completeSwitch = new AutoResetEvent(false);

        public AutoResetEvent ThreadComplete
        {
            get { return completeSwitch; }
            set { completeSwitch = value; }
        }


        public ClientConnection ClientConnection
        {
            get { return client; }
            set { client = value; }
        }
        
        
        
        

        public Download()
        {
            asyncUploadDelegate = new uploadAsyncDelegate(BeginClientUpload);
        }

        /// <summary>
        /// Begins upload to server
        /// First will ask user to select the file they want to upload
        /// </summary>
        public void UploadFile()
        {
            //byte[] data;
            this.socket.SendTimeout = 1000;
            OpenFileDialog ofd = new OpenFileDialog();
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                //data = System.IO.File.ReadAllBytes(ofd.FileName);
                filename = ofd.FileName;

                //this is not a asu
                this.asyncUploadDelegate.BeginInvoke(new AsyncCallback(UploadCompleteCallback), null);
                

                alive = true;
                return;
            }
            try
            {
                socket.Send(Encoding.ASCII.GetBytes("ENDUPLOAD"));
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + ex.TargetSite); 
            }
            
            DisconnectSocket();

            alive = false;
            return;

            
        }

        private void UploadCompleteCallback(IAsyncResult ar)
        {
            client.MessageInvoker("Upload Complete");
            this.ThreadComplete.Set();
            this.Alive = false;
        }

        public void StartConnection(IPAddress _serverIp)
        {
            socket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            socket.BeginConnect(_serverIp, 3334,new AsyncCallback(ConnectCallback),null);
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                socket.EndConnect(ar);

                client.MainThreadInvoker.Invoke(UploadFile);
            }
            catch (Exception ex)
            {

                MessageBox.Show("Connection Error " + ex.ToString() + ex.TargetSite + ex.Message);
            }
            

        }

        public void DisconnectSocket()
        {
            try
            {
                //socket.Shutdown(SocketShutdown.Both);
                //socket.Disconnect(true);
                //socket = null;
                //socket.Shutdown(SocketShutdown.Both);
                //socket.Dispose();
                
                socket.Shutdown(SocketShutdown.Both);
                socket.Disconnect(true);
                
                //socket = null;
                
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void BeginClientUpload()
        {
            //Get the File Size of the Upload 
            FileInfo FI = new FileInfo(filename);

            //initialize data for the synchronus upload process
            state.DataSent = 0;
            int dataSendSize = 10000;

            byte[] prefix = BitConverter.GetBytes(FI.Length);
            long previousStreamLocation = 0;

            //for the first send we need to send the prefix for the server to read the size of the file
            if (state.DataSent == 0 && serverReceiving)
            {
                //buffer used for the prefix sending
                byte[] prefixWriteBuffer = new byte[dataSendSize + 8];
                try
                {
                    //Write the prefix to the array before adding the data from the file
                    Array.Copy(prefix, prefixWriteBuffer, prefix.Length);
                    //add the data to the buffer
                    using (var stream = new FileStream(filename, FileMode.Open))
                    {
                        previousStreamLocation = stream.Position;
                        //check if the file is smaller than the default data send size
                        if (FI.Length <= dataSendSize)
                        {
                            Array.Resize(ref prefixWriteBuffer, (int)FI.Length);
                            stream.Read(prefixWriteBuffer, prefix.Length, (int)FI.Length - 8);
                        }
                        else
                        {
                            stream.Read(prefixWriteBuffer, prefix.Length, dataSendSize - 8);
                        }
                        
                    }

                    //Send the Data to the Server

                    try
                    {

                        socket.Send(prefixWriteBuffer, 0, prefixWriteBuffer.Length, SocketFlags.None);
                        state.DataSent += (dataSendSize);
                    }
                    catch (Exception ex)
                    {

                        
                        client.MessageInvoker("Failed To Send Data Over Connection...");
                        socket.Send(Encoding.ASCII.GetBytes("ENDUPLOAD"));
                        DisconnectSocket();
                        this.ThreadComplete.Set();
                        return;
                    }
                    

                }
                catch (Exception ex)
                {
                    socket.Send(Encoding.ASCII.GetBytes("ENDUPLOAD"));
                    MessageBox.Show("Error: " + ex.Message);
                    DisconnectSocket();
                    return;
                }
            }

            try
            {
                
                //do while the data that is sent in total is not = to the Files Length
                while (state.DataSent != FI.Length && serverReceiving)
                {
                    if (client.Connected)
                    {
                        byte[] writeBuffer = new byte[dataSendSize];
                        using (var stream = new FileStream(filename, FileMode.Open))
                        {
                            if ((previousStreamLocation + dataSendSize) <= FI.Length)
                            {
                                stream.Position = state.DataSent;
                                if ((stream.Position + dataSendSize) <= FI.Length)
                                {
                                    previousStreamLocation = stream.Position;
                                    stream.Read(writeBuffer, 0, dataSendSize);
                                }
                                else
                                {
                                    previousStreamLocation = stream.Position;
                                    stream.Read(writeBuffer, 0, (int)(FI.Length - stream.Position));
                                    Array.Resize(ref writeBuffer, (int)(FI.Length - previousStreamLocation));
                                }

                            }
                            else
                            {
                                stream.Position = state.DataSent;
                                stream.Read(writeBuffer, 0, (int)(state.DataSent - stream.Position));
                            }

                        }

                        try
                        {
                            socket.Send(writeBuffer, 0, writeBuffer.Length,
                                                        SocketFlags.None);
                        }
                        catch (Exception ex)
                        {

                            client.MessageInvoker("Failed To Send Data Over Connection...");
                            socket.Send(Encoding.ASCII.GetBytes("ENDUPLOAD"));
                            DisconnectSocket();
                            this.ThreadComplete.Set();
                            return;
                        }
                        
                        if ((state.DataSent + dataSendSize) <= FI.Length)
                        {
                            state.DataSent += dataSendSize;
                        }
                        else
                        {
                            //safe cast because will always be lower than 512
                            //dataSendSize = (int)(FI.Length - state.DataSent);
                            DisconnectSocket();
                            this.ThreadComplete.Set();
                            return;
                        }
                    }
                    

                
                }
                DisconnectSocket();
                this.ThreadComplete.Set();
                return;
            }
            catch (Exception ex)
            {
                try
                {
                    socket.Send(Encoding.ASCII.GetBytes("ENDUPLOAD"));
                }
                catch (Exception e)
                {

                    MessageBox.Show(e.Message + e.TargetSite);
                }
                
                DisconnectSocket();
                MessageBox.Show(ex.Message + ex.TargetSite);
                this.ThreadComplete.Set();
                return;
            }
        }
    }
}
